//==============================================================================
//
//          DTelNetClient - the telnet client class in the ofc-library
//
//               Copyright (C) 2005  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2005-06-01 05:12:47 $ $Revision: 1.8 $
//
//==============================================================================

#include "ofc/DTelNetClient.h"

 
#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DURL.h"
#include "ofc/DSocket.h"


#define DTNC_PORT     (23)

// Telnet commands
#define DTNC_IAC              (255)  // Interpret as command
#define DTNC_DONT             (254)  // Do not use this option (sender does not want receiver to use the option)
#define DTNC_DO               (253)  // Do use this option (sender wants receiver to use the option)
#define DTNC_WONT             (252)  // Will not use this option (sender will not use the option)
#define DTNC_WILL             (251)  // Will use this option (sender will use the option)
#define DTNC_SB               (250)  // Start subnegotiation
#define DTNC_GA               (249)  // Go ahead
#define DTNC_EL               (248)  // Erase line
#define DTNC_EC               (247)  // Erase character
#define DTNC_AYT              (246)  // Are you there
#define DTNC_AO               (245)  // Abort output
#define DTNC_IP               (244)  // Interrupt process
#define DTNC_BRK              (243)  // Break
#define DTNC_DM               (242)  // Data mark
#define DTNC_NOP              (241)  // No operation
#define DTNC_SE               (240)  // End subnegotiation

// Telnet options
#define DTNC_BINARY           (0)    // 8-bit data path
#define DTNC_ECHO             (1)    // Echo
#define	DTNC_RCP              (2)    // Prepare to reconnect
#define	DTNC_SGA              (3)    // Suppress go ahead
#define	DTNC_NAMS             (4)    // Approximate message size
#define	DTNC_STATUS           (5)    // Give status
#define	DTNC_TM               (6)    // Timing mark
#define	DTNC_RCTE             (7)    // Remote controlled transmission and echo
#define DTNC_NAOL             (8)    // Negotiate about output line width
#define DTNC_NAOP             (9)    // Negotiate about output page size
#define DTNC_NAOCRD           (10)   // Negotiate about CR disposition
#define DTNC_NAOHTS           (11)   // Negotiate about horizontal tabstops
#define DTNC_NAOHTD           (12)   // Negotiate about horizontal tab disposition
#define DTNC_NAOFFD           (13)   // Negotiate about formfeed disposition
#define DTNC_NAOVTS           (14)   // Negotiate about vertical tab stops
#define DTNC_NAOVTD           (15)   // Negotiate about vertical tab disposition
#define DTNC_NAOLFD           (16)   // Negotiate about output LF disposition
#define DTNC_XASCII           (17)   // Extended ascii character set
#define	DTNC_LOGOUT           (18)   // Force logout
#define	DTNC_BM               (19)   // Byte macro
#define	DTNC_DET              (20)   // Data entry terminal
#define	DTNC_SUPDUP           (21)   // Supdup protocol
#define	DTNC_SUPDUPOUTPUT     (22)   // Supdup output
#define	DTNC_SNDLOC           (23)   // Send location
#define	DTNC_TTYPE            (24)   // Terminal type
#define	DTNC_EOR              (25)   // End or record
#define	DTNC_TUID             (26)   // TACACS user identification
#define	DTNC_OUTMRK           (27)   // Output marking
#define	DTNC_TTYLOC           (28)   // Terminal location number
#define	DTNC_3270REGIME       (29)   // 3270 regime
#define	DTNC_X3PAD            (30)   // X.3 PAD
#define	DTNC_NAWS             (31)   // Window size
#define	DTNC_TSPEED           (32)   // Terminal speed
#define	DTNC_LFLOW            (33)   // Remote flow control
#define DTNC_LINEMODE         (34)   // Linemode option
#define DTNC_XDISPLOC         (35)   // X Display Location
#define DTNC_OLD_ENVIRON      (36)   // Old - Environment variables
#define	DTNC_AUTHENTICATION   (37)   // Authenticate
#define	DTNC_ENCRYPT          (38)   // Encryption option
#define DTNC_NEW_ENVIRON      (39)   // New - Environment variables
#define	DTNC_EXOPL            (255)  // Extended-options-list

#define DTNC_NEGOTIATE        (1)    // Option is under negotiation (initiated by the client)
#define DTNC_SERVER           (2)    // Option state for the server
#define DTNC_CLIENT           (4)    // Option state for the client

#define DTNC_OPTIONS          (256)  // the number of options

//
// The DTelNetClient class implements a telnet client. The class supports
// negotiation of options. Also subnegotiation is supported. There is not 
// yet support for interrupt sending.
//

@interface DTelNetClient : Object
{
@private
  DSocket            *_client;                 // the client socket
  BOOL                _connected;              // is the client connected ?
  int                 _options[DTNC_OPTIONS];  // the negotiated state op the options
  DData              *_request;                // the request to be sent to the server
}

#endif


@implementation DTelNetClient

  
#define DTNC_SEND             (1)    // Subnegotiation type 'send'
#define DTNC_IS               (0)    // Subnegotiation type 'is'

static const char *_commandStrings[] = 
{
    "SE",               // (240) End subnegotiation
    "NOP",              // (241) No operation
    "DM",               // (242) Data mark
    "BRK",              // (243) Break
    "IP",               // (244) Interrupt process
    "AO",               // (245) Abort output
    "AYT",              // (246) Are you there
    "EC",               // (247) Erase character
    "EL",               // (248) Erase line
    "GA",               // (249) Go ahead
    "SB",               // (250) Start subnegotiation
    "WILL",             // (251) Will use this option (sender will use the option)
    "WONT",             // (252) Will not use this option (sender will not use the option)
    "DO",               // (253) Do use this option (sender wants receiver to use the option)
    "DONT",             // (254) Do not use this option (sender does not want receiver to use the option)
    "IAC"               // (255) Interpret as command
};

static const char *_optionStrings[] = 
{
    "BINARY",          // (0)   8-bit data path
    "ECHO",            // (1)   Echo
    "RCP",             // (2)   Prepare to reconnect
    "SGA",             // (3)   Suppress go ahead
    "NAMS",            // (4)   Approximate message size
    "STATUS",          // (5)   Give status
    "TM",              // (6)   Timing mark
    "RCTE",            // (7)   Remote controlled transmission and echo
    "NAOL",            // (8)   Negotiate about output line width
    "NAOP",            // (9)   Negotiate about output page size
    "NAOCRD",          // (10)  Negotiate about CR disposition
    "NAOHTS",          // (11)  Negotiate about horizontal tabstops
    "NAOHTD",          // (12)  Negotiate about horizontal tab disposition
    "NAOFFD",          // (13)  Negotiate about formfeed disposition
    "NAOVTS",          // (14)  Negotiate about vertical tab stops
    "NAOVTD",          // (15)  Negotiate about vertical tab disposition
    "NAOLFD",          // (16)  Negotiate about output LF disposition
    "XASCII",          // (17)  Extended ascii character set
    "LOGOUT",          // (18)  Force logout
    "BM",              // (19)  Byte macro
    "DET",             // (20)  Data entry terminal
    "SUPDUP",          // (21)  Supdup protocol
    "SUPDUPOUTPUT",    // (22)  Supdup output
    "SNDLOC",          // (23)  Send location
    "TTYPE",           // (24)  Terminal type
    "EOR",             // (25)  End or record
    "TUID",            // (26)  TACACS user identification
    "OUTMRK",          // (27)  Output marking
    "TTYLOC",          // (28)  Terminal location number
    "3270REGIME",      // (29)  3270 regime
    "X3PAD",           // (30)  X.3 PAD
    "NAWS",            // (31)  Window size
    "TSPEED",          // (32)  Terminal speed
    "LFLOW",           // (33)  Remote flow control
    "LINEMODE",        // (34)  Linemode option
    "XDISPLOC",        // (35)  X Display Location
    "OLD_ENVIRON",     // (36)  Old - Environment variables
    "AUTHENTICATION",  // (37)  Authenticate
    "ENCRYPT",         // (38)  Encryption option
    "NEW_ENVIRON"      // (39)  New - Environment variables
};

//// Constructors

//
// Initialise the telnet client
//
// @return the object
//
- (DTelNetClient *) init
{
  int i;
  
  [super init];

  _client      = [DSocket new];
  _request     = [DData   new];
  
  _connected   = NO;

  for (i = 0; i < DTNC_OPTIONS; i++)
  {
    _options[i] = 0;
  }
  
  return self;
}


//// Deconstructor

//
// Free the telnet client object
//
// @return the object
//
- free
{
  
  [self close];
  
  [_client  free];
  [_request free];
  
  return [super free];
}


//// Copy related methods

//
// Deepen the object (not implemented)
//
//
- deepen
{
  WARNING(DW_METHOD_NOT_IMPL, "deepen");
  
  return self;
}


//// Class methods

//
// Return a string describing a command
// 
// @param command   the command
// 
// @return the describing string (or NULL)
// 
+ (const char *) commandToString :(unsigned char) command
{
  if (command >= DTNC_SE)
    return _commandStrings[command];
  else
    return NULL;
}

//
// Return a string describing an option
// 
// @param option    the option
// 
// @return the describing string (or NULL)
// 
+ (const char *) optionToString :(int) option
{
  if ((option >= DTNC_BINARY) && (option <= DTNC_NEW_ENVIRON))
    return _optionStrings[option];
  else
    return NULL;
}


//// Member methods

//
// Return the local client socket object
// 
// @return a reference to the client socket object
//
- (DSocket *) socket
{
  return _client;
}

//
// Check if there is a connection
// 
// @return is there a connection with the server ?
// 
- (BOOL) isConnected
{
  return _connected;
}

//
// Check the state of an option (for the server or client)
// 
// @param who      the side (DTNC_SERVER or DTNC_CLIENT)
// @param option   the option to be checked
// 
// @return the state 
// 
- (BOOL) option :(int) who :(int) option
{
  BOOL state = NO;

  if ((who != DTNC_SERVER) && (who != DTNC_CLIENT))
  {
    WARNING(DW_INVALID_ARG, "who");
  }
  else if ((option < 0) || (option >= DTNC_OPTIONS))
  {
    WARNING(DW_INVALID_ARG, "option");
  }
  else
  {
    state = ((_options[option] & who) != 0);
  }
  
  return state;
}


//// Connection methods

//
// Open the connection to the telnet server
// 
// @param server       the telnet server
// 
// @return success
// 
- (BOOL) open :(id <DSocketAddressable>) server
{
  BOOL ok = NO;

  if (_connected)
  {
    [self close];
  }
  
  ok  = [_client open :DSA_AF_INET :DSK_STREAM :[DSocket protocol :"tcp"]];

  //printf("Opened (%d)\n", ok);

  if (ok)
  {
    ok = [_client connect :server];
  }
    
  //printf("Connected (%d)\n" ok);

  if (ok)
  {
    _connected = YES;
  }
  
  return ok;
}
    
//
// Close the connection to the telnet server
// 
// @return success
// 
- (BOOL) close
{
  BOOL ok = [_client close];
  
  // printf("Closed (%d)\n", ok);

  _connected = NO;
  
  return ok;
}


//// Sending methods

//
// Put the open negotiation for an option message request in the send buffer.
// 
// @param who      the side the option has to implement (DTNC_SERVER, DTNC_CLIENT)
// @param option   the option to be negotiated
// @param state    the state for the option
// 
// @return success
// 
- (BOOL) requestOpenNegotiation :(int) who :(int) option :(BOOL) state
{
  BOOL ok = NO;
    
  if ((option < 0) && (option >= DTNC_OPTIONS))
  {
    WARNING(DW_INVALID_ARG, "option");
  }
  else if (!_connected)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else
  {
    int request = 0;
    
    if (who == DTNC_SERVER)
    {
      request = (state ? DTNC_DO : DTNC_DONT);
    }
    else if (who == DTNC_CLIENT)
    {
      request = (state ? DTNC_WILL : DTNC_WONT);
    }
    else
    {
      WARNING(DW_INVALID_ARG, "who");
    }
    
    if (request > 0)
    {
      [_request push :DTNC_IAC];
      [_request push :request ];
      [_request push :option  ];

      _options[option] |= DTNC_NEGOTIATE;
    
      ok = YES;
    }
  }
  
  return ok;
}

//
// Put the request for Sub Negotiation message in the send buffer
// 
// @param option   the option to be requested
// 
// @return success
// 
- (BOOL) requestSubNegotiation :(int) option
{
  BOOL ok = NO;
  
  if ((option < 0) || (option > DTNC_OPTIONS))
  {
    WARNING(DW_INVALID_ARG, "option");
  }
  else
  {
    [_request push :DTNC_IAC ];
    [_request push :DTNC_SB  ];
    [_request push :option   ];
    [_request push :DTNC_SEND];
    [_request push :DTNC_IAC ];
    [_request push :DTNC_SE  ];
    
    ok = YES;
  }
  
  return ok;
}

//
// Put the resonse Sub Negotiation message in the send buffer
// 
// @param option   the option to be responded
// @param data     the data for the sub negotiation
// @param length   the length of the data
// 
// @return success
// 
- (BOOL) respondSubNegotiation :(int) option :(unsigned char *) data :(int) length
{
  BOOL ok = NO;

  if ((option < 0) || (option >= DTNC_OPTIONS))
  {
    WARNING(DW_INVALID_ARG, "option");
  }
  else if ((data == NULL) || (length <= 0))
  {
    WARNING(DW_INVALID_ARG, "data/length");
  }
  else if ((_options[option] & DTNC_CLIENT) == 0)
  {
    WARNING(DW_INVALID_ARG, "option");
  }
  else
  {
    [_request push   :DTNC_IAC];
    [_request push   :DTNC_SB ];
    [_request push   :option  ];
    [_request push   :DTNC_IS ];
    [_request append :data :length];
    [_request push   :DTNC_IAC];
    [_request push   :DTNC_SE ];
    
    ok = YES;
  }
  
  return ok;
}

//
// Put the negotiation response message in the request buffer.
//
// @param who      the side that implements the option
// @param option   the option 
// @param accepted is the option accepted ?
// 
// @return success
// 
- (BOOL) respondNegotiation :(int) who :(int) option :(BOOL) accepted
{
  BOOL ok = NO;
  
  int request = 0;
  
  if ((option < 0) || (option >= DTNC_OPTIONS))
  {
    WARNING(DW_INVALID_ARG, "option");
  }
  else if (who == DTNC_SERVER)
  {
    request = (accepted ? DTNC_DO : DTNC_DONT);
  }
  else if (who == DTNC_CLIENT)
  {
    request = (accepted ? DTNC_WILL : DTNC_WONT);
  }
  else
  {  
    WARNING(DW_INVALID_ARG, "who");
  }
  
  if (request > 0)
  {
    [_request push :DTNC_IAC ];
    [_request push :request  ];
    [_request push :option   ];

    ok = YES;
  }
  
  return ok;
}

//
// Put Are You There in the send buffer
// 
// @return success
// 
- (BOOL) AYT
{
  [_request push :DTNC_IAC];
  [_request push :DTNC_AYT];
  
  return YES;
}

#if _PRIVATE_
#if 0
//
// Print the message
// 
// @param ch       the leading char
// @param data     the send/receive data
// @param length   the length of the data
// 
// @return none
//
static void _printMessage(char ch, const unsigned char *data, int length)
{    
  int i;

  printf("\n%c[", ch);
  for (i = 0; i < length; i++)
  {
    if ((i > 0) && (data[i-1] > 240))
      printf("<%03d>", data[i]);
    else if ((data[i] >= ' ') && (data[i] < 127))
      printf("%c", data[i]);
    else
      printf("<%03d>", data[i]);
  }
  printf("]\n");
}
#endif
#endif

//
// Put (normal) text in the send buffer and send the buffer to the server
// 
// @param text     the text for the server (or NULL)
// 
// @return success
// 
- (BOOL) sendText :(const unsigned char *) text
{
  BOOL ok = YES;

  if (text != NULL)
  {
    // Put the text in the request buffer, doubling IAC
    // and insuring CR LF
    while (*text != EOS) 
    {
      if (*text == '\r')
      {
        [_request push :*text++];
        
        if (*text == '\n')
        {
          [_request push :*text++];
        }
        else
        {
          [_request push :'\n'];
        }
      }
      else 
      {
        if (*text == '\n')
        {
          [_request push :'\r'];
        }
        else if (*text == DTNC_IAC)
        {
          [_request push :DTNC_IAC];
        }
        
        [_request push :*text++];
      }
    }
  }
  
  if ([_request length] > 0)
  {
    //_printMessage('S', [_request data], [_request length]);
        
    ok = ([_client send :(void *) [_request data] :[_request length] :0] > 0);
        
    [_request clear];
  }
  
  return ok;
}

//
// Check if the send buffer has pending messages (requests or responses)
// 
// @return are there ?
// 
- (BOOL) pendingRequests
{
  return ([_request length] > 0);
}

//
// Check if there are pending negotiations with the server
// 
// @return are there ?
// 
- (BOOL) pendingNegotiations
{
  int  option;
  
  for (option = 0; option < DTNC_OPTIONS; option++)
  {
    if ((_options[option] & DTNC_NEGOTIATE) != 0)
    {
      return YES;
    }
  }

  return NO;
}


//// Receiving methods

//
// Wait (blocking) for a response of the server. Depending on the contents of
// the response, the methods processOpenNegotiation, processSubNegotiation 
// and/or processSpecialCommand will be called.
//
// @return a (new) DData object with the received data (without the (sub) 
// negotiations, nil for error) 
// 
- (DData *) receive 
{
  DData *received = [_client recv :16384 :0];
  DData *response = nil;
  BOOL   ok       = NO;
  
  if (received != nil)
  {
    response = [DData new];
    
    //_printMessage('R', [received data], [received length]);
        
    ok = [self _scanMessage :response :[received data] :[received length]];
    
    [received free];
  }
  
  return response;
}

//
// Wait (blocking) for a response of the server. Depending on the contents of
// the response, the methods processOpenNegotiation, processSubNegotiation 
// and/or processSpecialCommand will be called. The remaining data will be 
// placed in the response object.
//
// @param  response  the DData object to be filled with the received data
// 
// @return success
// 
- (BOOL) receive :(DData *) response
{
  BOOL ok = NO;

  if (response == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "response");
  }
  else
  {
    DData *received = [_client recv :16384 :0];
    
    [response clear];
  
    if (received != nil)
    {
      //_printMessage('R', [received data], [received length]);
    
      ok = [self _scanMessage :response :[received data] :[received length]];
    
      [received free];
    }
  }
  
  return ok;
}

// 
// Process the receiving of a special command (to be overridden)
//
// @param command  the received (telnet) command
// 
// @return success
// 
- (BOOL) processSpecialCommand :(unsigned char) command
{
  return YES;
}

//
// Process the receiving of the open negotiation request from the server (to be overridden, default: not supported)
//
// @param who       the side that implements the option (DTNC_SERVER, DTNC_CLIENT)
// @param option    the option to be negotiated
// @param state     the suggested state for the option
// 
// @return is the option supported ?
// 
- (BOOL) processOpenNegotiation :(int) who :(int) option :(int) state
{
  return NO;
}

//
// Process the receiving of an accepted negotiation response from the server (to be overridden)
// 
// @param who       the side that implements the option (DTNC_SERVER, DTNC_CLIENT)
// @param option    the option that is negotiated with the server
// @param accepted  is the option accepted ?
// 
// @return success
// 
- (BOOL) processResponseNegotiation :(int) who :(int) option :(BOOL) accepted
{
  return YES;
}


//
// Process the request for a sub negotiation from the server. Use 
// sendResponseSubNegotiation to reply to this request (to be overridden)
// 
// @param option   the option in the request for sub negotiation
//
// @return success
// 
- (BOOL) processRequestSubNegotiation :(int) option
{
  return YES;
}

//
// Process the response for a sub negotiation from the server (to be overridden)
// 
// @param option   the option that was sub negotiated
// @param data     the data of the sub negotiation
// @param length   the length of the data
// 
// @return success
// 
- (BOOL) processResponseSubNegotiation :(int) option :(const unsigned char *) data :(int) length
{
  return YES;
}


//// Message processing methods (private methods)

//
// Scan the received message
// 
// @param data     the received data
// @param length   the length of the data
// 
// @return a (new) DData object with the received data (or nil for no data)
//
- (BOOL) _scanMessage :(DData *) response :(const unsigned char *) data :(int) length
{
  BOOL ok = YES;

  if (data != NULL)
  {
    while (length > 0)
    {
      while ((*data != DTNC_IAC) && (length > 0))
      {
        [response push :*data];
        
        data++;
        length--;
      }
      
      if (length > 0)
      {
        data++;
        length--;
        
        if (length > 0)
        {
          switch (*data)
          {
            case DTNC_IAC :
              [response push :*data]; // doubled IAC
              data++; length--;
              break;
            
            case DTNC_DONT:
            case DTNC_DO  :
            case DTNC_WONT:
            case DTNC_WILL:
              ok &= [self _scanNegotiation :&data :&length];
              break;
            
            case DTNC_SB  :
              data++; length--;
            
              ok &= [self _scanSubNegotiation :&data :&length];
              break;
            
            case DTNC_GA  : 
            case DTNC_AYT :
            case DTNC_AO  :
            case DTNC_IP  :
            case DTNC_BRK :
            case DTNC_DM  :
            case DTNC_NOP :
            case DTNC_EL  :
            case DTNC_EC  :
              ok &= [self processSpecialCommand :*data];
              data++; length--;
              break;
            
            case DTNC_SE  :
            default       :
              WARNING(DW_UNKNOWN_WARNING, "unprocessed command");
              data++;
              length--;
              break;
          }
        }
      }
    }
  }
  
  return ok;
}

//
// Scan a negotiation message
// 
// @param data     the received data
// @param length   the length of the data
// 
// @return success
//
- (BOOL) _scanNegotiation :(const unsigned char **) data :(int *) length
{
  BOOL ok = YES;
  
  const unsigned char *pntr = *data;
  
  int request = *pntr++;
  
  (*length)--;
  
  if (*length > 0)
  {
    int option = *pntr++;
    
    (*length)--;
    
    if ((option >= 0) && (option < DTNC_OPTIONS))
    {
      if ((_options[option] & DTNC_NEGOTIATE) != 0) // server responded on client negotiation
      {
        if (request == DTNC_DO)
        {
          _options[option] |= DTNC_CLIENT;
          
          ok &= [self processResponseNegotiation :DTNC_CLIENT :option :YES];
        }
        else if (request == DTNC_WILL)
        {
          _options[option] |= DTNC_SERVER;
          
          ok &= [self processResponseNegotiation :DTNC_SERVER :option :YES];
        }
        else if (request == DTNC_DONT)
        {
          _options[option] &= (~DTNC_CLIENT);
          
          ok &= [self processResponseNegotiation :DTNC_CLIENT :option :NO];
        }
        else if (request == DTNC_WONT)
        {
          _options[option] &= (~DTNC_SERVER);
          
          ok &= [self processResponseNegotiation :DTNC_SERVER :option :NO];
        }
        _options[option] &= (~DTNC_NEGOTIATE);
      }
      else // server start negotiation
      {
        int who;
        int state;
        
        who   = ((request == DTNC_DO) || (request == DTNC_DONT)) ? DTNC_CLIENT : DTNC_SERVER;
        state = ((request == DTNC_DO) || (request == DTNC_WILL)) ? YES         : NO;
        
        if ([self processOpenNegotiation :who :option :state])
        {
          if (state)
          {
            _options[option] |= who;
          
            ok &= [self respondNegotiation :who :option :YES];
          }
          else
          {
            _options[option] &= (~who);
            
            ok &= [self respondNegotiation :who :option :NO];
            
            WARNING(DW_UNKNOWN_WARNING, "refusal cannot be negotiated to an acceptance");
          }
        }
        else
        {
          _options[option] &= (~who);
          
          ok &= [self respondNegotiation :who :option :NO];
        }
      }
    }
    else
    {
      WARNING(DW_UNKNOWN_WARNING, "unsupported option");
    }
  }
  *data = pntr;
  
  return ok;
}

//
// Scan a sub negotiation message
// 
// @param data     the received data
// @param length   the length of the data
// 
// @return success
//
- (BOOL) _scanSubNegotiation :(const unsigned char **) data :(int *) length
{
  BOOL ok = NO;
  
  const unsigned char *pntr = *data;

  if (*length > 0)
  {
    int option = *pntr++;
    
    (*length)--;
    
    if ((option >= 0) && (option <= DTNC_OPTIONS))
    {
      if (*length > 0)
      {
        int type = *pntr++;
        
        (*length)--;
        
        if (type == DTNC_SEND)
        {
          if ((*length > 1) && (pntr[0] == DTNC_IAC) && (pntr[1] == DTNC_SE))
          {
            *length -= 2;
             pntr   += 2;
            
            ok = [self processRequestSubNegotiation :option];
          }
        }
        else if (type == DTNC_IS)
        {
          int count = 0;
          
          while ((count <= (*length-2)) && (pntr[count] != DTNC_IAC) && (pntr[count+1] != DTNC_SE))
          {
            count++;
          }
          
          if (pntr[count] == DTNC_IAC)
          {
            ok = [self processResponseSubNegotiation :option :pntr :count];
            
             pntr   += (count+2);
            *length -= (count+2);
          }
        }
      }
    }
  }
  
  if (!ok)
  {
    WARNING(DW_UNKNOWN_WARNING, "wrong formatted SubNegotiation");
  }
  
  *data = pntr;
  
  return ok;
}

@end

/*===========================================================================*/
